package me.kagami.myjvm.jvm;

import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FrameNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.LabelNode;

public class Frame {

    private final Object[] operands; // Operand stack
    private final Object[] locals;   // Local variables
    private int stackPtr;            // Operand stack pointer

    private final InsnList instructions;
    private AbstractInsnNode programCtr;

    private Object returnObj;

    public Frame(int maxStack, int maxLocals, InsnList instructions, Object[] args) {
        this.operands = new Object[maxStack];
        this.locals = new Object[maxLocals];
        this.programCtr = null;
        this.instructions = instructions;
        for (int i = 0; i < args.length; i++) {
            store(i, args[i]);
        }
    }

    public void push(Object value) {
        this.operands[this.stackPtr++] = value;
    }

    public <T> T pop(Class<T> clazz) {
        Object operand = this.operands[--this.stackPtr];
        this.operands[this.stackPtr] = null;
        return clazz.cast(operand);
    }

    public void store(int var, Object value) {
        this.locals[var] = value;
    }

    public <T> T load(int var, Class<T> clazz) {
        return clazz.cast(this.locals[var]);
    }


    public InsnList getInstructions() {
        return instructions;
    }

    public AbstractInsnNode getProgramCtr() {
        return programCtr;
    }

    public boolean next() {
        if (this.programCtr == null) {
            this.programCtr = getNext(instructions.getFirst());
            return true;
        }
        AbstractInsnNode next = getNext(this.programCtr);
        if (next != null) {
            this.programCtr = next;
            return true;
        }
        return false;
    }

    private static AbstractInsnNode getNext(AbstractInsnNode insn) {
        AbstractInsnNode next = insn.getNext();
        while (next != null && next.getOpcode() == -1) {
            next = next.getNext();
        } // Note: if next == null the loop condition becomes false
        return next;
    }

    public Object[] getLocals() {
        return locals;
    }

    public Object getReturnObj() {
        return returnObj;
    }

    public void setReturnObj(Object returnObj) {
        this.returnObj = returnObj;
    }

    public Object[] getOperands() {
        return operands;
    }
}
